#include <mono/common/path.h>
#include <mono/common/string_tokenizer.h>
#include <mono/common/ascii.h>
#include <mono/common/file.h>
#include <mono/common/env.h>
#include <algorithm>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

#  pragma GCC diagnostic ignored "-Wshadow"

#define PATH_MAX 1024
namespace mono {


Path::Path(): _absolute(false)
{
}


Path::Path(bool absolute): _absolute(absolute)
{
}


Path::Path(const std::string& path)
{
	assign(path);
}


Path::Path(const char* path)
{
	assign(path);
}


Path::Path(const Path& path): 
	_node(path._node), 
	_device(path._device),
	_name(path._name),
	_version(path._version),
	_dirs(path._dirs),
	_absolute(path._absolute)
{	
}


Path::Path(const Path& parent, const std::string& fileName):
	_node(parent._node), 
	_device(parent._device),
	_name(parent._name),
	_version(parent._version),
	_dirs(parent._dirs),
	_absolute(parent._absolute)
{	
	makeDirectory();
	_name = fileName;
}


Path::Path(const Path& parent, const char* fileName):
	_node(parent._node), 
	_device(parent._device),
	_name(parent._name),
	_version(parent._version),
	_dirs(parent._dirs),
	_absolute(parent._absolute)
{	
	makeDirectory();
	_name = fileName;
}


Path::Path(const Path& parent, const Path& relative):
	_node(parent._node), 
	_device(parent._device),
	_name(parent._name),
	_version(parent._version),
	_dirs(parent._dirs),
	_absolute(parent._absolute)
{	
	resolve(relative);
}


Path::~Path()
{
}

	
Path& Path::operator = (const Path& path)
{
	return assign(path);
}

	
Path& Path::operator = (const std::string& path)
{
	return assign(path);
}


Path& Path::operator = (const char* path)
{
	return assign(path);
}


void Path::swap(Path& path)
{
	std::swap(_node, path._node);
	std::swap(_device, path._device);
	std::swap(_name, path._name);
	std::swap(_version, path._version);
	std::swap(_dirs, path._dirs);
	std::swap(_absolute, path._absolute);
}


Path& Path::assign(const Path& path)
{
	if (&path != this)
	{
		_node     = path._node;
		_device   = path._device;
		_name     = path._name;
		_version  = path._version;
		_dirs     = path._dirs;
		_absolute = path._absolute;
	}
	return *this;
}


Path& Path::assign(const std::string& path)
{

	parse(path);
	return *this;
}


Path& Path::assign(const char* path)
{
	return assign(std::string(path));
}


std::string Path::toString() const
{
	return build();
}

	

bool Path::tryParse(const std::string& path)
{
	Path p;
	p.parse(path);
	if(!p.valid()) {
		return false;
	}
	assign(p);
	return true;
}


Path& Path::parseDirectory(const std::string& path)
{
	assign(path);
	return makeDirectory();
}



Path& Path::makeDirectory()
{
	pushDirectory(_name);
	_name.clear();
	_version.clear();
	return *this;
}


Path& Path::makeFile()
{
	if (!_dirs.empty() && _name.empty())
	{
		_name = _dirs.back();
		_dirs.pop_back();

	}
	return *this;
}


Path& Path::makeAbsolute()
{
	return makeAbsolute(current());
}


Path& Path::makeAbsolute(const Path& base)
{
	if (!_absolute) {
		Path tmp = base;
		tmp.makeDirectory();
		for (StringVec::const_iterator it = _dirs.begin(); it != _dirs.end(); ++it)
		{
			tmp.pushDirectory(*it);
		}
		_node     = tmp._node;
		_device   = tmp._device;
		_dirs     = tmp._dirs;
		_absolute = base._absolute;
	}
	return *this;
}


Path Path::absolute() const
{
	Path result(*this);
	if (!result._absolute) {
		result.makeAbsolute();
	}
	return result;
}


Path Path::absolute(const Path& base) const
{
	Path result(*this);
	if (!result._absolute) {
		result.makeAbsolute(base);
	}
	return result;
}


Path Path::parent() const
{
	Path p(*this);
	return p.makeParent();
}


Path& Path::makeParent()
{
	if (_name.empty()) {
		if (_dirs.empty()) {
			if (!_absolute)
				_dirs.push_back("..");
		} else {
			if (_dirs.back() == "..")
				_dirs.push_back("..");
			else
				_dirs.pop_back();
		}
	} else {
		_name.clear();
		_version.clear();
	}
	return *this;
}


Path& Path::append(const Path& path)
{
	makeDirectory();
	_dirs.insert(_dirs.end(), path._dirs.begin(), path._dirs.end());
	_name = path._name;
	_version = path._version;
	return *this;
}


Path& Path::resolve(const Path& path)
{
	if (path.isAbsolute())
	{
		assign(path);
	}
	else
	{
		for (int i = 0; i < path.depth(); ++i)
			pushDirectory(path[i]);
		_name = path._name;
	}
	return *this;
}


Path& Path::setNode(const std::string& node)
{
	_node     = node;
	_absolute = _absolute || !node.empty();
	return *this;
}

	
Path& Path::setDevice(const std::string& device)
{
	_device   = device;
	_absolute = _absolute || !device.empty();
	return *this;
}

	
const std::string& Path::directory(size_t n) const
{
	assert (n <= _dirs.size());
	
	if (n < _dirs.size())
		return _dirs[n];
	else
		return _name;	
}


const std::string& Path::operator [] (size_t n) const
{
	assert (n <= _dirs.size());
	
	if (n < _dirs.size())
		return _dirs[n];
	else
		return _name;	
}

	
Path& Path::pushDirectory(const std::string& dir)
{
	if (!dir.empty() && dir != ".") {
		if (dir == "..") {
			if (!_dirs.empty() && _dirs.back() != "..") {
				_dirs.pop_back();
			} else if (!_absolute) {
				_dirs.push_back(dir);
			}
		} else {
			 _dirs.push_back(dir);
		}
	}
	return *this;
}

	
Path& Path::popDirectory()
{
	assert (!_dirs.empty());
	
	_dirs.pop_back();
	return *this;
}


Path& Path::popFrontDirectory()
{
	assert (!_dirs.empty());
	
	StringVec::iterator it = _dirs.begin();
	_dirs.erase(it);
	return *this;
}

	
Path& Path::setFileName(const std::string& name)
{
	_name = name;
	return *this;
}


Path& Path::setBaseName(const std::string& name)
{
	std::string ext = getExtension();
	_name = name;
	if (!ext.empty()) {
		_name.append(".");
		_name.append(ext);
	}
	return *this;
}


std::string Path::getBaseName() const
{
	std::string::size_type pos = _name.rfind('.');
	if (pos != std::string::npos)
		return _name.substr(0, pos);
	else
		return _name;
}


Path& Path::setExtension(const std::string& extension)
{
	_name = getBaseName();
	if (!extension.empty())
	{
		_name.append(".");
		_name.append(extension);
	}
	return *this;
}

			
std::string Path::getExtension() const
{
	std::string::size_type pos = _name.rfind('.');
	if (pos != std::string::npos)
		return _name.substr(pos + 1);
	else
		return std::string();
}


Path& Path::clear()
{
	_node.clear();
	_device.clear();
	_name.clear();
	_dirs.clear();
	_version.clear();
	_absolute = false;
	return *this;
}


std::string Path::current()
{
	std::string path;
	char cwd[PATH_MAX];
	if (getcwd(cwd, sizeof(cwd))) {
		path = cwd;
	} else {
		path = "";
	}
	std::string::size_type n = path.size();
	if (n > 0 && path[n - 1] != '/') path.append("/");
	return path;
}

	
std::string Path::home()
{
	std::string path;
	struct passwd* pwd = getpwuid(getuid());
	if (pwd) {
		path = pwd->pw_dir;
	} else {
		pwd = getpwuid(geteuid());
		if (pwd)
			path = pwd->pw_dir;
		else
			path = Environment::get("HOME");
	}
	std::string::size_type n = path.size();
	if (n > 0 && path[n - 1] != '/') path.append("/");
	return path;
}

	
std::string Path::temp()
{
	std::string path;
	char* tmp = getenv("TMPDIR");
	if (tmp) {
		path = tmp;
		std::string::size_type n = path.size();
		if (n > 0 && path[n - 1] != '/') path.append("/");
	} else {
		path = "/tmp/";
	}
	return path;
}


std::string Path::null()
{
	return "/dev/null";
}

	
std::string Path::expand(const std::string& path)
{
	std::string result;
	std::string::const_iterator it  = path.begin();
	std::string::const_iterator end = path.end();
	if (it != end && *it == '~') {
		++it;
		if (it != end && *it == '/') {
			result += home(); ++it;
		} else {
			result += '~';
		}
	}
	while (it != end) {
		if (*it == '$') {
			std::string var;
			++it;
			if (it != end && *it == '{') {
				++it;
				while (it != end && *it != '}') var += *it++;
				if (it != end) ++it;
			} else {
				while (it != end && (Ascii::isAlphaNumeric(*it) || *it == '_')) var += *it++;
			}
			char* val = getenv(var.c_str());
			if (val) {
				result += val;
			}
		} else {
			result += *it++;
		}
	}
	return result;
}


void Path::listRoots(std::vector<std::string>& roots)
{
	roots.clear();
	roots.push_back("/");
}


bool Path::find(StringVec::const_iterator it, StringVec::const_iterator end, const std::string& name, Path& path)
{
	while (it != end) {
		Path p(*it);
		p.makeDirectory();
		p.resolve(Path(name));
		if(!p.valid()) {
			++it;
			continue;	
		}
		File f(p);
		if (f.exists()) {
			path = p;
			return true;
		}
		++it;
	}
	return false;
}


bool Path::find(const std::string& pathList, const std::string& name, Path& path)
{
	StringTokenizer st(pathList, std::string(1, pathSeparator()), StringTokenizer::TOK_IGNORE_EMPTY + StringTokenizer::TOK_TRIM);
	return find(st.begin(), st.end(), name, path);
}


void Path::parse(const std::string& path)
{
	clear();

	std::string::const_iterator it  = path.begin();
	std::string::const_iterator end = path.end();
	//if path is empty, every thing is empty
	if (it != end) {
		if (*it == '/') {
			_absolute = true; ++it;
		} else if (*it == '~') {
			++it;
			if (it == end || *it == '/') {
				Path cwd(home());
				_dirs = cwd._dirs;
				_absolute = true;
			} else {
				--it;
			}
		}

		while (it != end)
		{
			std::string name;
			while (it != end && *it != '/') {
				name += *it++;
			}
			if (it != end) {
				if (_dirs.empty()) {
					if (!name.empty() && *(name.rbegin()) == ':') {
						_absolute = true;
						_device.assign(name, 0, name.length() - 1);
					} else {
						pushDirectory(name);
					}
				} else {
					pushDirectory(name);
				}
			} else {
				_name = name;
			}
			if (it != end) {
				++it;
			}
		}
	}
}

bool Path::valid()
{
	return _dirs.empty() || !_name.empty() || !_device.empty();
}

std::string Path::build() const
{
	std::string result;
	if (!_device.empty()) {
		result.append("/");
		result.append(_device);
		result.append(":/");
	} else if (_absolute) {
		result.append("/");
	}

	for (StringVec::const_iterator it = _dirs.begin(); it != _dirs.end(); ++it)
	{
		result.append(*it);
		result.append("/");
	}
	result.append(_name);
	return result;
}



std::string Path::transcode(const std::string& path)
{
	return path;
}


} // namespace Poco
